﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Core.Services
{
	using System;
	using System.Collections.Generic;
	using System.Web;
	using Falcon.Core.Collections;
	using Falcon.Core.Domain;
	using Falcon.Core.Domain.Enums;
	using Falcon.Core.Domain.Extensions;
	using Falcon.Core.Domain.Repositories;
	using Falcon.Core.Domain.Services;
	using Falcon.Core.Domain.Values;
	using Falcon.Core.Email;
	using Falcon.Core.Extensions;
	using Falcon.Core.Helpers;

	public class UserService : IUserService
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="UserService"/> class.
		/// </summary>
		/// <param name="emailService">The email service.</param>
		/// <param name="userRoleService">The user role service.</param>
		/// <param name="userPermissionService">The user permission service.</param>
		/// <param name="userPreferenceService">The user preference service.</param>
		/// <param name="userProfileService">The user profile service.</param>
		/// <param name="userRepository">The user repository.</param>
		public UserService(
			IEmailService emailService,
			IUserRoleService userRoleService,
			IUserPermissionService userPermissionService,
			IUserPreferenceService userPreferenceService,
			IUserProfileService userProfileService,
			IUserRepository userRepository)
		{
			this.EmailService = emailService;
			this.UserRoleService = userRoleService;
			this.UserPermissionService = userPermissionService;
			this.UserPreferenceService = userPreferenceService;
			this.UserProfileService = userProfileService;
			this.UserRepository = userRepository;
		}

		/// <summary>
		/// Gets or sets the email service.
		/// </summary>
		/// <value>The email service.</value>
		public IEmailService EmailService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the user role service.
		/// </summary>
		/// <value>The user role service.</value>
		public IUserRoleService UserRoleService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the user permission service.
		/// </summary>
		/// <value>The user permission service.</value>
		public IUserPermissionService UserPermissionService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the user preference service.
		/// </summary>
		/// <value>The user preference service.</value>
		public IUserPreferenceService UserPreferenceService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the user profile service.
		/// </summary>
		/// <value>The user profile service.</value>
		public IUserProfileService UserProfileService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the user repository.
		/// </summary>
		/// <value>The user repository.</value>
		public IUserRepository UserRepository
		{
			get; 
			private set;
		}

		/// <summary>
		/// Gets the user by the specified specification.
		/// </summary>
		/// <param name="specification">The <see cref="UserSpecification"/>.</param>
		/// <returns></returns>
		public User GetBy(UserSpecification specification)
		{
			return this.UserRepository.GetBy(specification);
		}

		/// <summary>
		/// Gets the user by the specified id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public User GetById(int id)
		{
			return this.UserRepository.GetById(id);
		}

		/// <summary>
		/// Gets all the users by the specified specification.
		/// </summary>
		/// <param name="specification">The <see cref="UserSpecification"/>.</param>
		/// <returns></returns>
		public IList<User> GetAll(UserSpecification specification)
		{
			return this.UserRepository.GetAll(specification);
		}

		/// <summary>
		/// Gets all the users by the specified specification using paging.
		/// </summary>
		/// <param name="specification">The <see cref="UserSpecification"/>.</param>
		/// <returns></returns>
		public IPagedList<User, UserSpecification> GetPaged(UserSpecification specification)
		{
			return this.UserRepository.GetPaged(specification);
		}

		/// <summary>
		/// Gets the user session.
		/// </summary>
		/// <param name="name">The name (identity name).</param>
		/// <returns></returns>
		public UserSession GetSession(string name)
		{
			if (name.Equals(User.Guest))
			{
				return this.Guest();
			}

			var user = this.GetBy(new UserSpecification
			{
				EmailAddress = name
			});

			if (user == null)
			{
				return this.Guest();
			}

			user.LastVisit = DateTime.Now;

			this.Update(user);

			var permissions = this.UserPermissionService.GetAll(new UserPermissionSpecification
			{
				UserId = user.Id
			});

			return new UserSession
			{
				Id = user.Id,
				Name = user.Name,
				Slug = user.Slug,
				TimeZone = user.Preference.TimeZone.Title,
				Messages = user.Preference.Messages,
				IsAdministrator = permissions.Has(Roles.Administrators),
				IsModerator = permissions.Has(Roles.Moderators),
				Dst = user.Preference.Dst,
				TimeFormat = user.Preference.TimeFormat,
				Permissions = permissions
			};
		}

		/// <summary>
		/// Guests this instance.
		/// </summary>
		/// <returns></returns>
		public UserSession Guest()
		{
			return new UserSession
			{
				Name = User.Guest,
				Slug = User.Guest.Slug(),
				TimeZone = Settings.TimeZones.DefaultTitle,
				Dst = UserPreferenceDaylightSavingTime.Auto,
				TimeFormat = UserPreferenceTimeFormat.Relative,
				Permissions = new List<UserPermission>
				{
					new UserPermission
					{
						Role = this.UserRoleService.GetBy(new UserRoleSpecification
						{
							Title = Roles.Guests
						}),
						Action = UserPermissionActions.View
					}
				}
			};
		}

		/// <summary>
		/// Creates the user.
		/// </summary>
		/// <returns></returns>
		public User Create()
		{
			var user = new User
			{
				Posts = 0,
				Created = DateTime.Now
			};

			user.Updated = user.Created;
			user.LastVisit = user.Created;
			user.LastLogOn = user.Created;

			return user;
		}

		/// <summary>
		/// Gets the users count by the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public int Count(UserSpecification specification)
		{
			return this.UserRepository.Count(specification);
		}

		/// <summary>
		/// Inserts the user into the storage.
		/// </summary>
		/// <param name="user">The <see cref="User"/>.</param>
		public void Insert(User user)
		{
			user.Validate();

			var salt = new RandomHelper().Create(Settings.Users.PasswordSaltLength);

			user.Slug = user.Name.Slug();
			user.Password = user.Password.HashToSha1(salt).ToHex();
			user.Salt = salt;

			this.UserRepository.Insert(user);
		}

		/// <summary>
		/// Insert the user into the storage.
		/// </summary>
		/// <param name="user">The <see cref="User"/>.</param>
		/// <param name="value">The <see cref="UserCreate"/> value to insert the user with.</param>
		public void Insert(User user, UserCreate value)
		{
			value.Validate();

			user.SkipValidation = true;

			var preference = this.UserPreferenceService.Create();

			preference.CountryId = value.CountryId;
			preference.TimeZoneId = value.TimeZoneId;
			preference.Dst = value.Dst;
			preference.StartOfWeek = value.StartOfWeek;
			preference.TimeFormat = value.TimeFormat;

			this.UserPreferenceService.Insert(preference);

			var profile = this.UserProfileService.Create();

			profile.Title = Resources.Titles.Member;

			this.UserProfileService.Insert(profile);

			user.PreferenceId = preference.Id;
			user.ProfileId = profile.Id;
			user.Name = value.Name;
			user.EmailAddress = value.EmailAddress;
			user.HostAddress = GetHostAddress();
			user.Password = value.Password;
			user.Status = value.Status;

			this.Insert(user);

			this.EmailService.Send(new UserRegistrationEmail
			{
				To = user.EmailAddress,
				Name = user.Name
			});
		}

		/// <summary>
		/// Updates the user in the storage.
		/// </summary>
		/// <param name="user">The <see cref="User"/>.</param>
		public void Update(User user)
		{
			user.Validate();

			user.Slug = user.Name.Slug();
			user.Updated = DateTime.Now;

			this.UserRepository.Update(user);
		}

		/// <summary>
		/// Updates the user in the storage.
		/// </summary>
		/// <param name="user">The <see cref="User"/>.</param>
		/// <param name="value">The <see cref="UserUpdate"/> value to update the user with.</param>
		public void Update(User user, UserUpdate value)
		{
			value.Validate();

			user.SkipValidation = true;

			user.Name = value.Name;
			user.EmailAddress = value.EmailAddress;
			user.Status = value.Status;

			this.Update(user);
		}

		/// <summary>
		/// Deletes the user in the storage.
		/// </summary>
		/// <param name="user">The <see cref="User"/>.</param>
		public void Delete(User user)
		{
			this.UserRepository.Delete(user);
		}

		/// <summary>
		/// Gets the host address.
		/// </summary>
		/// <returns></returns>
		private static string GetHostAddress()
		{
			if (!Application.IsWebRequest)
			{
				return "127.0.0.1";
			}

			var host = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

			if (!host.HasValue())
			{
				host = HttpContext.Current.Request.UserHostAddress;
			}

			return (host.HasValue()) ? host : "127.0.0.1";
		}
	}
}